package com.googlecode.imanager.swing;

import java.awt.Rectangle;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;

import javax.swing.JComponent;

import com.googlecode.imanager.client.Container;
import com.googlecode.imanager.client.Control;
import com.googlecode.imanager.client.Manager;
import com.googlecode.imanager.client.action.Action;
import com.googlecode.imanager.client.bridge.IControl;
import com.googlecode.imanager.client.bridge.style.IBackground;
import com.googlecode.imanager.client.bridge.style.IBorder;
import com.googlecode.imanager.client.bridge.style.IStyle;
import com.googlecode.imanager.client.event.KeyEvent;
import com.googlecode.imanager.client.event.KeyEventType;
import com.googlecode.imanager.client.event.MouseEvent;
import com.googlecode.imanager.client.event.MouseEventType;
import com.googlecode.imanager.client.style.Color;
import com.googlecode.imanager.client.style.Style;
import com.googlecode.imanager.client.style.listener.StyleAttribute;
import com.googlecode.imanager.core.base.AbstractControl;
import com.googlecode.imanager.core.base.BridgeApplication;
import com.googlecode.imanager.core.base.style.ComputedTheme;
import com.googlecode.imanager.swing.util.SwingBorder;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public abstract class SwingControl extends AbstractControl implements IControl { 

    protected final SwingBorder border;
    protected final JComponent target;
    protected int updatingSize;

    public SwingControl(BridgeApplication bridge, Control source) {
        super(bridge, source);
        this.border = new SwingBorder(computedStyle);
        this.target = createTarget();
    }
    
    @Override
    protected void start() {
        synchronized (target.getTreeLock()) {
            buildImplementation();
            defineLayout();
            doAttachEvents();
            doInitialAssignment();
            super.start();
        }
    }

    protected void buildImplementation() {
    }

    protected void defineLayout(){
    }

    protected abstract JComponent createTarget();
    
    @Override
    public JComponent getTarget(){
        return target;
    }
    
    public JComponent getTargetContained(){
        return target;
    }
    
    public JComponent getTargetBorder(){
        return getTargetContained();
    }

    public JComponent getTargetBackground(){
        return target;
    }

    public JComponent getTargetMouseEvent(){
        return target;
    }
    
    public JComponent getTargetKeyEvent(){
        return target;
    }
    
    @Override
    protected void destroy() {
        java.awt.Container parent = getTargetContained().getParent();
        if (parent != null){
            parent.remove(getTargetContained());
        }
        super.destroy();
    }

    @Override
    public SwingApplication getBridgeApplication() {
        return (SwingApplication) application;
    }
    
    @Override
    public ComputedTheme getComputedTheme() {        
        return getParentComputedTheme();
    }

    @Override
    public void setParent(Container parent){
        java.awt.Component child = getTargetContained();
        java.awt.Container oldParent = child.getParent();
        
        if (oldParent != null){
            oldParent.remove(child);
        }
        
        if (parent != null){
            ((SwingContainer) Manager.getImpl(parent)).getTargetContainer().add(child);
        }
    }

    protected void doAttachEvents() {
        doAttachMouseEvents();
        doAttachKeyEvents();
    }
    
    protected void doAttachMouseEvents() {        
        getTargetMouseEvent().addMouseListener(new MouseListener() {
            
            // TODO gerenciar os eventos 
            //      - definir o target certo
            //      - verificar se o swing não está lançando eventos dos filhos para os pais
            
            private void fireMouseEvent(java.awt.event.MouseEvent e, MouseEventType type){
                synchronized (TREE_LOCK) {
                    if (!isDropped()){
                        startFireUIEvent();
                        try {
                            int clicks;
                            
                            // a lógica presente no bloco abaixo  
                            // é para normalizar o evento MOUSE_CLICK
                            
                            // FIXME testar exaustivamente

                            if (type == MouseEventType.MOUSE_CLICK){                                
                                if (e.getClickCount() != 2){
                                    return;
                                }
                                
                                clicks = 2;                                
                            } else {
                                clicks = 0;
                                
                                if (type == MouseEventType.MOUSE_UP){
                                    if (e.getClickCount() != 2
                                            && e.getX() >= 0
                                            && e.getY() >= 0
                                            && e.getX() < getTargetMouseEvent().getWidth()
                                            && e.getY() < getTargetMouseEvent().getHeight()){

                                        // fires MOUSE_UP
                                        getBridgeApplication().fireMouseEvent(
                                                getSource(), e.getX(), e.getY(), clicks, type);

                                        // fires MOUSE_CLICK
                                        type = MouseEventType.MOUSE_CLICK;
                                        clicks = 1;
                                    }
                                }
                            }

                            MouseEvent event = getBridgeApplication().fireMouseEvent(
                                    getSource(), e.getX(), e.getY(), clicks, type);
                            
                            if (event.isPreventDefault()){
                                // FIXME e.consume(); // test if not needed
                            }
                        } catch (Exception ex) {
                            processException(ex);
                        } finally {
                            endFireUIEvent();
                        }
                    }
                }
            }
            
            @Override
            public void mouseReleased(java.awt.event.MouseEvent e) {
                fireMouseEvent(e, MouseEventType.MOUSE_UP);
            }
            
            @Override
            public void mousePressed(java.awt.event.MouseEvent e) {
                fireMouseEvent(e, MouseEventType.MOUSE_DOWN);
            }
            
            @Override
            public void mouseExited(java.awt.event.MouseEvent e) {
                fireMouseEvent(e, MouseEventType.MOUSE_EXIT);
            }
            
            @Override
            public void mouseEntered(java.awt.event.MouseEvent e) {
                fireMouseEvent(e, MouseEventType.MOUSE_ENTER);
            }
            
            @Override
            public void mouseClicked(java.awt.event.MouseEvent e) {
                fireMouseEvent(e, MouseEventType.MOUSE_CLICK);
            }
            
        });
    }

    protected void doAttachKeyEvents() {
        getTargetKeyEvent().addKeyListener(new KeyListener() {
            
            // TODO gerenciar os eventos 
            //      - definir o target certo
            //      - verificar se o swing não está lançando eventos dos filhos para os pais
            
            private void fireKeyEvent(java.awt.event.KeyEvent e, KeyEventType type){
                synchronized (TREE_LOCK) {
                    if (!isDropped()){
                        startFireUIEvent();
                        try {
                            KeyEvent event = getBridgeApplication().fireKeyEvent(
                                    getSource(), e.getKeyCode(), e.getKeyChar(), type);
                            if (event.isPreventDefault()){
                                e.consume();
                            }
                        } catch (Exception ex) {
                            processException(ex);
                        } finally {
                            endFireUIEvent();
                        }
                    }
                }
            }
            
            @Override
            public void keyTyped(java.awt.event.KeyEvent e) {
                fireKeyEvent(e, KeyEventType.KEY_PRESSED);
            }
            
            @Override
            public void keyReleased(java.awt.event.KeyEvent e) {
                fireKeyEvent(e, KeyEventType.KEY_UP);
            }
            
            @Override
            public void keyPressed(java.awt.event.KeyEvent e) {
                fireKeyEvent(e, KeyEventType.KEY_DOWN);
            }
            
        });
    }

    @Override
    public void click() {
        getManager().fireClick(getSource());
    }

    @Override
    public void setBounds(int left, int top, int width, int height) {
        if (updatingSize == 0){
            updatingSize++;
            try {                
                setBoundsExt(left, top, width, height);
            } finally {
                updatingSize--;
            }
        }
    }
    
    public void setBoundsExt(int left, int top, int width, int height) {
        getTargetContained().setBounds(left, top, width, height);
        getTargetContained().validate();
    }

    public void setVisible(Boolean visible) {
        getTargetContained().setVisible(visible == null ? false : visible);
    }

    @Override
    public void setDisabled(Boolean disabled) {
        getTargetContained().setEnabled(!(disabled == null ? false : disabled));
    }

    public void setHint(String hint) {
        if ("".equals(hint)){
            hint = null;
        }
        getTargetContained().setToolTipText(hint);
    }

    @Override
    public void setAction(Action action) {
        // TODO Auto-generated method stub
    }

    @Override
    public void setStyle(Style style) {
        // nothing to do
    }

    @Override
    public void scrollToRectVisible(int left, int top, int width, int height) {
        if (width < 1){
            width = 1;
        }
        if (height < 1){
            height = 1;
        }
        getTargetContained().scrollRectToVisible(new Rectangle(left, top, width, height));
    }

    public void realign() {
        getTargetContained().revalidate();
        getTargetContained().repaint();
    }
    
    public void refresh() {
    }
    
    public void repaint(){
        getTargetContained().repaint();
    }
    
    @Override
    protected <T> void doComputeStyle(StyleAttribute<T> attribute) {
        super.doComputeStyle(attribute);
        doComputedStyleAux(attribute);
        repaint();
    }

    protected <T> void doComputedStyleAux(StyleAttribute<T> attribute){
        Class<T> type = attribute.getAttributeType();
        
        if (type == IStyle.class){
            computeBackground(getTargetBackground());
            computeBorder(getTargetBorder());
        }
        else if (type == IBackground.class){
            computeBackground(getTargetBackground());
        }
        else if (type == IBorder.class){
            computeBorder(getTargetBorder());
        }
    }

    protected void computeBorder(JComponent target) {
        target.setBorder(null);
        
        if (!border.isEmpty()){
            target.setBorder(border);
        }
    }

    protected void computeBackground(JComponent target) {
        Color color = computedStyle.getBackground().getColor();
        
        if (color == null){
            color = Color.TRANSPARENT;
        }
        
        if (color.getAlpha() > 0){
            if (source instanceof Container){
                target.setBackground(null);
            } else {
                target.setBackground(new java.awt.Color(color.getRed(), 
                        color.getGreen(), color.getBlue(), 255-color.getAlpha()));
            }
        } else {
            target.setBackground(new java.awt.Color(color.getRGB()));
        }
    }

}
